/* ===== semantic.c ===== */
#include <string>
#include <iostream>
#include <map>
#include <list>
#include <vector>

using namespace std;

#include <stdio.h>
#include <stdlib.h>

#include "ptype.hh"
#include "symtab.hh"
#include "codegest.hh"

#include "myASTnode.hh"

#include "util.hh"
#include "semantic.hh"

#include "codegen.hh"

// symbol table with information about identifiers in the program
// declared in symtab.cc
extern symtab symboltable;

// When dealing with a list of instructions, it contains the maximum auxiliar space
// needed by any of the instructions for keeping non-referenceable non-basic-type values.
int maxoffsetauxspace;

// When dealing with one instruction, it contains the auxiliar space
// needed for keeping non-referenceable values.
int offsetauxspace;

// For dit(r)nghishing different labels for different if's and while's.
int newLabelWhile(bool inicialitzar = false)
{
    static int comptador = 1;
    if (inicialitzar) comptador = 0;
    return comptador++;
}

int newLabelIf(bool inicialitzar = false)
{
    static int comptador = 1;
    if (inicialitzar) comptador = 0;
    return comptador++;
}

int newLabelShort(bool inicialitzar = false)
{
    static int comptador = 1;
    if (inicialitzar) comptador = 0;
    return comptador++;
}

string comma() {
    return "wris \",\"";
}

// Too lazy to write that much
inline string itos(const int n) { return " " + itostring(n); }
inline string t(const int n) { return " t" + itostring(n); }
inline string soffset(const string scope, const string name) { return " offset(" + scope + ":_" + name + ")"; }

int compute_size(ptype tp)
{
    if (isbasickind(tp->kind))
    {
        tp->size = 4;
    }
    else if (tp->kind == "array")
    {
        tp->size = tp->numelemsarray * compute_size(tp->down);
    }
    else if (tp->kind == "struct")
    {
        tp->size = 0;
        for (list<string>::iterator it = tp->ids.begin(); it != tp->ids.end(); it++)
        {
            tp->offset[*it] = tp->size;
            tp->size += compute_size(tp->struct_field[*it]);
        }
    }
    return tp->size;
}

void gencodevariablesandsetsizes(scope *sc, codesubroutine &csubr, bool isfunction = 0)
{
    if (isfunction) csubr.parameters.push_back("returnvalue");
    for (list<string>::iterator it = sc->ids.begin(); it != sc->ids.end(); it++)
    {
        if (sc->m[*it].kind == "idvarlocal")
        {
            variable_data vardata;
            vardata.name = "_" + (*it);
            vardata.size = compute_size(sc->m[*it].tp);
            csubr.localvariables.push_back(vardata);
        }
        else if (sc->m[*it].kind == "idparval" || sc->m[*it].kind == "idparref")
        {
            compute_size(sc->m[*it].tp);
            csubr.parameters.push_back("_" + (*it));
        }
        else if (sc->m[*it].kind == "idfunc")
        {
            // Here it is assumed that in tp->right is kept the return value type
            // for the case of functions. If this is not the case you must
            // change this line conveniently in order to force the computation
            // of the size of the type returned by the function.
            compute_size(sc->m[*it].tp->right);
        }
        else if (sc->m[*it].kind == "idproc")
        {
            // Nothing to be done.
        }
    }
    csubr.parameters.push_back("static_link");
}

codechain indirections(int jumps, int r)
{
    codechain c;
    
    if (jumps == 0)
    {
        c = "aload static_link" + t(r);
    }
    else
    {
        c = "load static_link" + t(r);
        for (int i = 1; i < jumps; i++)
            c = c || "load" + t(r) + t(r);
    }
    return c;
}

codechain GenAddress(AST *a, int r);
codechain GenValue(AST *a, int r);

void CodeGenRealParams(AST *a, ptype param_def, codechain &pushparam, codechain &removeparam, int r)
{
    AST* ident = child(a, 0);
    AST* param_list = child(a, 1);

    if (!a) return;

    int jumps = symboltable.jumped_scopes(ident->text);

    for (AST *param = child(param_list, 0); param != 0; param = param->right)
    {
        // Passed by value
        if (param_def->kind == "parval")
            pushparam =
                pushparam ||
                GenValue(param, r);
        // Passed by reference
        else
            pushparam =
                pushparam ||
                GenAddress(param, r);
        pushparam =
            pushparam ||
            "pushparam" + t(r);
        removeparam =
            "killparam" ||
            removeparam;
        // Next
        param_def = param_def->right;
    }

    // Static link is passed as the last parameter
    pushparam =
        pushparam ||
        indirections(jumps, r) ||
        "pushparam" + t(r);
    removeparam =
        "killparam" ||
        removeparam;
}

codechain GenAddress(AST *a, int r)
{
    int s = r + 1;
    codechain c;

    if (!a) return c;

    if (a->kind == "ident")
    {
        string kind = symboltable[a->text].kind;
        string subrid = symboltable.idtable(a->text);
        int jumps = symboltable.jumped_scopes(a->text);

        if (jumps == 0)
        {
            if (kind == "idvarlocal" || (kind == "idparval" && isbasickind(a->tp->kind)))
                c = "aload _" + a->text + t(r);
            else
                c = "load _" + a->text + t(r);
        }
        else
        {
            c = indirections(jumps, r) ||
                "addi" + t(r) + soffset(subrid, a->text) + t(r);

            if (kind == "idparref" || (kind == "idparval" && !isbasickind(a->tp->kind)))
                c = c ||
                    "load" + t(r) + t(r);
        }
        // FIXME cout << a->text << " from '" << symboltable.top()->idtable << "' is (" << subrid << ":_" << a->text << ")^" << jumps << endl;
    }
    else if (a->kind == ".")
    {
        AST *expr = child(a, 0);
        AST *ident = child(a, 1);
        string offset = itos(expr->tp->offset[ident->text]);
        c = GenAddress(expr, r) ||
            "addi" + t(r) + offset + t(r);
    }
    else if (a->kind == "[")
    {
        AST *expr = child(a, 0);
        AST *index = child(a, 1);
        string size = itos(compute_size(expr->tp) / expr->tp->numelemsarray);
        c = GenAddress(expr, r) ||
            GenValue(index, s) ||
            "muli" + t(s) + size + t(s) ||
            "addi" + t(r) + t(s) + t(r);
    }
    else
        cout << "BIG ADDRESS PROBLEM! No case defined for kind " << a->kind << endl;

    return c;
}

codechain GenValue(AST *a, int r)
{
    int s = r + 1;
    codechain c;

    if (!a) return c;

    // I intended to follow the same structure I used in semantic.cc
    // but this two are so common that it's worth having them outside
    AST *lexpr = child(a, 0);
    AST *rexpr = child(a, 1);

    if (a->ref)
    {
        string kind = symboltable[a->text].kind;
        string subrid = symboltable.idtable(a->text);
        int jumps = symboltable.jumped_scopes(a->text);

        // Local IDs of basic types that are not by reference can be optimised
        if (jumps == 0 && isbasickind(a->tp->kind) && (kind == "idvarlocal" || kind == "idparval"))
        {
            c = "load _" + a->text + t(r);
        }
        // Basic type
        else if (isbasickind(a->tp->kind))
        {
            c = GenAddress(a, r) ||
                "load" + t(r) + t(r);
        }
        // Array or Struct must be copied
        else
        {
            c = GenAddress(a, s) ||
                "aload aux_space" + t(r) ||
                "addi" + t(r) + itos(offsetauxspace) + t(r) ||
                "copy" + t(s) + t(r) + itos(compute_size(a->tp));

            offsetauxspace += a->tp->size;
            if (maxoffsetauxspace < offsetauxspace)
                maxoffsetauxspace = offsetauxspace;
        }
        // FIXME cout << a->text << " from '" << symboltable.top()->idtable << "' is (" << subrid << ":_" << a->text << ")^" << jumps << endl;
    }
    else if (a->kind == "intconst")
    {
        c = "iload " + a->text + t(r);
    }
    else if (a->kind == "true")
    {
        c = "iload 1" + t(r);
    }
    else if (a->kind == "false")
    {
        c = "iload 0" + t(r);
    }
    else if (a->kind == "+")
    {
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "addi" + t(r) + t(s) + t(r);
    }
    else if (a->kind == "-")
    {
        if (rexpr == 0)
            c = GenValue(lexpr, r) ||
                "mini" + t(r) + t(r);
        else
            c = GenValue(lexpr, r) ||
                GenValue(rexpr, s) ||
                "subi" + t(r) + t(s) + t(r);
    }
    else if (a->kind == "*")
    {
        string kind = symboltable[a->text].kind;
        if (kind == "array")
        {
                
            string etiq  = "_" + itostring(newLabelShort());
            string size = itos(compute_size(lexpr->tp));
            c = GenAddress(rexpr, 0) || // initial position t0
                GenAddress(lexpr, 1) || // initial position t1
                "iload" + size + " t9" || // total size
                "addi t1 t9 t2 " || // final position t2
                "iload 4 t3" || // increment t3
                "iload 0 t11" ||
                "etiq swhile" + etiq ||
                "subi t2 t1 t9" ||
                "fjmp t9 endswhile" + etiq ||
                "load t1 t9" ||
                "load t0 t8" ||
                "muli t9 t8 t10" ||
                "addi t11 t10 t11" ||
                "stor t9 t1" ||
                    //"wrii t0" || comma() ||
                    //"wrii t1" || comma() ||
                    //"wrii t2" || comma() ||
                    //"wrii t9" || comma() ||
                    //"wris \";\"" ||
                "addi t1 t3 t1" ||
                "addi t0 t3 t0" ||
                "ujmp swhile" + etiq ||
                "etiq endswhile"+ etiq;
        }
        else
        {
            c = GenValue(lexpr, r) ||
                GenValue(rexpr, s) ||
                "muli" + t(r) + t(s) + t(r);
        }
    }
    else if (a->kind == "/")
    {
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "divi" + t(r) + t(s) + t(r);
    }
    else if (a->kind == "<")
    {
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "lesi" + t(r) + t(s) + t(r);
    }
    else if (a->kind == ">")
    {
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "grti" + t(r) + t(s) + t(r);
    }
    else if (a->kind == "=")
    {
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "equi" + t(r) + t(s) + t(r);
    }
    else if (a->kind == "and")
    {
        string etiq  = "_" + itostring(newLabelShort());
        c = GenValue(lexpr, r) ||
            "fjmp" + t(r) + " short" + etiq ||
            GenValue(rexpr, s) ||
            "land" + t(r) + t(s) + t(r) ||
            "etiq short" + etiq;
/*
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "land" + t(r) + t(s) + t(r);
*/    }
    else if (a->kind == "or")
    {
        string etiq  = "_" + itostring(newLabelShort());
        c = GenValue(lexpr, r) ||
            "lnot" + t(r) + t(s) ||
            "fjmp" + t(s) + " short" + etiq ||
            GenValue(rexpr, s) ||
            "loor" + t(r) + t(s) + t(r) ||
            "etiq short" + etiq;
/*
        c = GenValue(lexpr, r) ||
            GenValue(rexpr, s) ||
            "loor" + t(r) + t(s) + t(r);
*/  }
    else if (a->kind == "not")
    {
        c = GenValue(lexpr, r) ||
            "lnot" + t(r) + t(r);
    }
    else if (a->kind == ".")
    {
        AST *expr = child(a, 0);
        AST *ident = child(a, 1);
        string offset = itos(expr->tp->offset[ident->text]);
        c = GenValue(expr, r) ||
            "addi" + t(r) + offset + t(r);
    }
    else if (a->kind == "(")
    {
        AST* ident = child(a, 0);
        AST* param_list = child(a, 1);
        ptype tp = symboltable[ident->text].tp;
        string callid = symboltable.idtable(ident->text);
        codechain pushparam, removeparam;

        // Returns basic type, just push before and pop after
        if (isbasickind(tp->right->kind))
        {
            pushparam = 
                "pushparam 0";
            removeparam =
                "popparam" + t(r);
        }
        // Returns Struct or Array, needs aux_space to copy data
        else
        {
            // Push aux_space address
            pushparam =
                "aload aux_space" + t(r) ||
                "addi" + t(r) + itos(offsetauxspace) + t(r) ||
                "pushparam" + t(r);
            removeparam =
                "killparam";
            r++;
            // Update aux_space offset
            offsetauxspace += tp->right->size;
            if (maxoffsetauxspace < offsetauxspace)
                maxoffsetauxspace = offsetauxspace;
        }

        CodeGenRealParams(a, tp->down, pushparam, removeparam, r);

        c = pushparam ||
            "call " + callid + "_" + ident->text ||
            removeparam;
    }
    else
        cout << "BIG VALUE PROBLEM! No case defined for kind " << a->kind << endl;

    return c;
}

codechain CodeGenInstruction(AST *a, string info = "")
{
    codechain c;

    if (!a) return c;

    // I intended to follow the same structure I used in semantic.cc
    // but this two are so common that it's worth having them outside
    AST *lexpr = child(a, 0);
    AST *rexpr = child(a, 1);

    offsetauxspace = 0;
    if (a->kind == "list")
    {
        for (AST *item = a->down; item != 0; item = item->right)
            c = c ||
                CodeGenInstruction(item, info);
    }
    else if (a->kind == "(")
    {
        AST* ident = child(a, 0);
        AST* param_list = child(a, 1);
        ptype tp = symboltable[ident->text].tp;
        string callid = symboltable.idtable(ident->text);
        codechain pushparam, removeparam;

        CodeGenRealParams(a, tp->down, pushparam, removeparam, 0);

        c = pushparam ||
            "call " + callid + "_" + ident->text ||
            removeparam;
    }
    else if (a->kind == ":=")
    {
        if (isbasickind(lexpr->tp->kind))
            c = GenAddress(lexpr, 0) ||
                GenValue(rexpr, 1) ||
                "stor t1 t0";
        else if (rexpr->ref)
            c = GenAddress(lexpr, 0) ||
                GenAddress(rexpr, 1) ||
                "copy t1 t0" + itos(compute_size(rexpr->tp));
        else
            c = GenAddress(lexpr, 0) ||
                GenValue(rexpr, 1) ||
                "copy t1 t0" + itos(compute_size(rexpr->tp));
    }
    else if (a->kind == "*=")
    {
        string etiq  = "_" + itostring(newLabelShort());
        string size = itos(compute_size(lexpr->tp));
        c = GenValue(rexpr, 0) || // value to add t0
            GenAddress(lexpr, 1) || // initial position t1
            "iload" + size + " t9" || // total size
            "addi t1 t9 t2 " || // final position t2
            "iload 4 t3" || // increment t3
            "etiq swhile" + etiq ||
            "subi t2 t1 t9" ||
            "fjmp t9 endswhile" + etiq ||
            "load t1 t9" ||
            "muli t9 t0 t9" ||
            "stor t9 t1" ||
                //"wrii t0" || comma() ||
                //"wrii t1" || comma() ||
                //"wrii t2" || comma() ||
                //"wrii t9" || comma() ||
                //"wris \";\"" ||
            "addi t1 t3 t1" ||
            "ujmp swhile" + etiq ||
            "etiq endswhile"+ etiq;
    }
    else if (a->kind == "++")
    {
        
    }
    else if (a->kind == "if")
    {
        AST *condition = child(a, 0);
        AST *tinstrs = child(a, 1);
        AST *einstrs = child(a, 2);
        string etiq  = "_" + itostring(newLabelIf());
        // if then else endif
        if (einstrs != 0)
            c = GenValue(condition, 0) ||
                "fjmp t0 else" + etiq ||
                CodeGenInstruction(tinstrs, info) ||
                "ujmp endif" + etiq ||
                "etiq else" + etiq ||
                CodeGenInstruction(einstrs, info) ||
                "etiq endif" + etiq;
        // if then endif
        else
            c = GenValue(condition, 0) ||
                "fjmp t0 endif" + etiq ||
                CodeGenInstruction(tinstrs, info) ||
                "etiq endif" + etiq;
    }
    else if (a->kind == "while")
    {
        AST *condition = child(a, 0);
        AST *instrs = child(a, 1);
        string etiq  = "_" + itostring(newLabelWhile());
        c = "etiq while" + etiq ||
            GenValue(condition, 0) ||
            "fjmp t0 endwhile" + etiq ||
            CodeGenInstruction(instrs, info) ||
            "ujmp while" + etiq ||
            "etiq endwhile" + etiq;
    }
    else if (a->kind == "write" || a->kind == "writeln")
    {
        if (child(a, 0)->kind == "string")
            c = c ||
                "wris " + child(a, 0)->text;
        else
            c = GenValue(child(a, 0), 0) ||
                "wrii t0";

        if (a->kind == "writeln")
            c = c ||
                "wrln";
    }

    return c;
}

void CodeGenSubroutine(AST *a, list<codesubroutine> &csubrlist)
{
    AST *dec = child(a, 0);
    AST *params = child(dec, 0);
    AST *vars = child(a, 1);
    AST *blocks = child(a, 2);
    AST *instr = child(a, 3);
    AST *ret = child(a, 4);
    
    // Update symboltable to current scope
    string id = symboltable.idtable(dec->text) + "_" + dec->text;
    symboltable.push(a->sc);
    symboltable.setidtable(id);
    
    // Generate code for subroutines
    for (AST *block = child(blocks, 0); block != 0; block = block->right)
        CodeGenSubroutine(block, csubrlist);

    // Generate current routine code
    codesubroutine cs;
    cs.name = id;
    bool is_function = ret != 0;
    maxoffsetauxspace = 0;
    newLabelIf(true);
    newLabelWhile(true);

    // Local variables and parameters
    gencodevariablesandsetsizes(a->sc, cs, is_function);
    
    // Code
    cs.c = CodeGenInstruction(instr);
    
    // Return
    if (is_function)
    {
        // Basic type
        if (isbasickind(dec->tp->right->kind))
            cs.c = cs.c ||
                GenValue(ret, 0) ||
                "stor t0 returnvalue";
        // Struct or Array must be copied
        else
            cs.c = cs.c ||
                GenAddress(ret, 1) ||
                "load returnvalue t0" ||
                "copy t1 t0" + itos(compute_size(ret->tp));
    }
    cs.c = cs.c ||
        "retu";

    // Add aux_space to program localvariables
    if (maxoffsetauxspace > 0)
    {
        variable_data vardata;
        vardata.name = "aux_space";
        vardata.size = maxoffsetauxspace;
        cs.localvariables.push_back(vardata);
    }

    // Restore scope and add current routine to the list
    symboltable.pop();
    csubrlist.push_back(cs);
}

void CodeGen(AST *a, codeglobal &cglobal)
{
    AST *vars = child(a, 0);
    AST *blocks = child(a, 1);
    AST *instr = child(a, 2);
    
    // Update symboltable to program scope
    initnumops();
    securemodeon();
    symboltable.push(a->sc);
    symboltable.setidtable("program");

    // Generate code for subroutines
    for (AST *block = child(blocks, 0); block != 0; block = block->right)
        CodeGenSubroutine(block, cglobal.l);
    
    // Generate code for program
    cglobal.mainsub.name = "program";
    maxoffsetauxspace = 0;
    newLabelIf(true);
    newLabelWhile(true);
    
    // Local variables and parameters
    gencodevariablesandsetsizes(a->sc, cglobal.mainsub);
    
    // Code
    cglobal.mainsub.c = CodeGenInstruction(instr) || "stop";
    
    // Add aux_space to program localvariables
    if (maxoffsetauxspace > 0)
    {
        variable_data vardata;
        vardata.name = "aux_space";
        vardata.size = maxoffsetauxspace;
        cglobal.mainsub.localvariables.push_back(vardata);
    }
    
    // Restore scope
    symboltable.pop();
}

